#include <head.h>

#define MAX_FDS 10000
#define BUF_SIZE 1024

// 消息结构体
typedef struct msg
{
    uint32_t ip;
    uint16_t port;
    char buf[1000]; // 消息的内容
} MSG;

int sockArr[3000];                // 保存套接字的数组
struct sockaddr_in addrArr[3000]; // 保存地址的数组
int count = 0;                    // 当前有多少客户端

// 把字符串IP转为数字IP
uint32_t stringIPtoNum(char *pIP)
{
    uint32_t nIP = 0;
    // AF_INET是代表IPV4地址的意思，AF_INET6代表IPV6
    inet_pton(AF_INET, pIP, &nIP);
    return nIP;
}

// 把数字IP转为字符串IP
void numIPtoString(uint32_t ip, char *psIP, int size)
{
    inet_ntop(AF_INET, &ip, psIP, size);
}

int main()
{
    // 创建一个SOCKET，并且返回一个套接字的文件描述符
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1)
    {
        perror("");
    }

    int val = 1; // 1代表打开地址重复利用功能，0代表关闭
    // SOL_SOCKET是通用socket设置的意思
    // SO_REUSEADDR是地址重复利用的意思
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));

    // 地址结构体，需要填写IP，端口，地址族（IPV4/IPV6）
    //  typedef struct sockaddr_in MYADDR;
    struct sockaddr_in addr;
    addr.sin_addr.s_addr = stringIPtoNum("192.168.70.61"); // 指定IP
    addr.sin_port = htons(9000);                           // 指定端口
    addr.sin_family = AF_INET;                             // 指定地址协议族

    // 第二个参数因为历史原因，系统设定的是struct sockaddr类型，而现在常用的类型是struct sockaddr_in
    // 所以这里要做一个强制转换
    if (-1 == bind(fd, (struct sockaddr *)&addr, sizeof(addr)))
    {
        perror("");
    }
    // 启动监听功能，允许5个客户端排队等待
    if (-1 == listen(fd, 5))
    {
        perror("");
    }

    // 创建一个EPOLL对象
    int epollfd = epoll_create1(0);
    if (epollfd < 0)
    {
        perror("epoll_create1");
        return 1;
    }

    // 事件结构体里面主要填写两个变量，一个是感兴趣额的事件，另一个是套接字
    struct epoll_event ev; // 定义了一个事件结构体变量
    ev.data.fd = fd;       // 把监听套接字设置到事件结构体中
    ev.events = EPOLLIN;   // 把套接字感兴趣的事件设置为EPOLLIN（输入/可读事件）
    // 控制epoll对象，把套接字和它对应的结构体加入到epoll对象中
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) < 0)
    {
        perror("epoll_ctl");
        close(fd);
    }

    struct epoll_event evArr[MAX_FDS];
    while (1)
    {
        // 等待在epoll对象中的套接字发生你填写在结构体中指定的事件发生，如果发生了就会把所有发生事件的套接字对应的结构体存入第二个参数（数组）中
        // 返回值为发生感兴趣的事件的套接字数量
        int ret = epoll_wait(epollfd, evArr, MAX_FDS, -1);
        if (ret < 0)
        {
            perror("epoll_wait");
        }
        else if (ret > 0)
        {
            for (int i = 0; i < ret; i++)
            {
                if (evArr[i].data.fd == fd) // 判断结构体中的套接字是否是监听套接字
                {
                    struct sockaddr_in clientAddr; // 用来存放客户端的地址(来电显示)
                    socklen_t len = sizeof(clientAddr);
                    // 阻塞等待客户端的链接，如果有客户链接上该函数才会返回，否则会一个等待
                    int newSocketFd = accept(fd, (struct sockaddr *)&clientAddr, &len);
                    if (newSocketFd == -1)
                    {
                        perror("");
                    }

                    addrArr[count] = clientAddr;
                    sockArr[count] = newSocketFd;
                    count++;
                    // 事件结构体里面主要填写两个变量，一个是感兴趣额的事件，另一个是套接字
                    struct epoll_event ev;    // 定义了一个事件结构体变量
                    ev.data.fd = newSocketFd; // 把监听套接字设置到事件结构体中
                    ev.events = EPOLLIN;      // 把套接字感兴趣的事件设置为EPOLLIN（输入/可读事件）
                    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, newSocketFd, &ev) < 0)
                    {
                        perror("epoll_ctl");
                        close(fd);
                    }

                    char szIP[16]; // 把手的数字IP转诶字符串IP，用来打印
                    numIPtoString(clientAddr.sin_addr.s_addr, szIP, sizeof(szIP));

                    printf("又添加了一个套接字和结构体到epoll对象中 %s %d\n", szIP, ntohs(clientAddr.sin_port));
                    // printf("接受了新的链接，并且产生了新的套接字，所以最大的文件描述符也要更新一下，值为:%d\n",newSocketFd);
                }
                else // 通信套接字
                {
                    MSG m1;
                    int ret = read(evArr[i].data.fd, &m1, sizeof(m1)); // 既然套接字有可读事件，那么就读取套接字上的内容
                    if (ret <= 0)                                      // 如果接收数据出错了，就把套接字关闭，并且从集合中删除掉
                    {
                        close(evArr[i].data.fd);
                        // 事件结构体里面主要填写两个变量，一个是感兴趣额的事件，另一个是套接字
                        struct epoll_event ev;         // 定义了一个事件结构体变量
                        ev.data.fd = evArr[i].data.fd; // 把监听套接字设置到事件结构体中
                        ev.events = EPOLLIN;           // 把套接字感兴趣的事件设置为EPOLLIN（输入/可读事件）
                        // 控制epoll对象，从里面删除出错的套接字和结构体
                        if (epoll_ctl(epollfd, EPOLL_CTL_DEL, evArr[i].data.fd, &ev) < 0)
                        {
                            perror("epoll_ctl");
                        }

                        // 在这里还需要在地址数组和套接字数组中删除对应的内容
                        // 在这里还需要在地址数组和套接字数组中删除对应的内容
                        // 在这里还需要在地址数组和套接字数组中删除对应的内容
                        for (int j = 0; j < count; j++)
                        {
                            if (sockArr[j] == evArr[i].data.fd)
                            {
                                sockArr[j] = sockArr[count - 1];
                                count--;
                            }
                        }
                        
                        



                        printf("%d号套接字对应的客户端退出了\n", ev.data.fd);
                    }
                    else
                    {
                        printf("%s\n", m1.buf);
                        // 遍历所有已经连接的用户
                        for (int j = 0; j < count; j++)
                        {
                            // 判断地址数组中的地址是否和需要发送消息的地址相同
                            if (addrArr[j].sin_addr.s_addr == m1.ip && addrArr[j].sin_port == m1.port)
                            {
                                send(sockArr[j], &m1, sizeof(m1), 0);
                            }
                        }
                    }
                }
            }
        }
    }

    return 0;
}